package cc.eddic.practice.concurrency.impl;

import cc.eddic.practice.concurrency.CDNLogItem;
import cc.eddic.practice.concurrency.CDNLogParser;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;

@Component
@Qualifier("CDNLogParserWithLoopCxyImpl")
public class CDNLogParserWithLoopCxyImpl implements CDNLogParser {
    private static final int THREAD_POOL_SIZE = Runtime.getRuntime().availableProcessors();

    public Map<String, List<CDNLogItem>> parse(String folderName) {
        Map<String, List<CDNLogItem>> resultMap = new ConcurrentHashMap<>();
        ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);
        CompletionService<Model> completionService = new ExecutorCompletionService<>(executorService);
        List<Path> gzippedFiles = new ArrayList<>();

        try {
            Files.walkFileTree(Paths.get(folderName), EnumSet.noneOf(FileVisitOption.class), Integer.MAX_VALUE,
                    new GzFileVisitor(completionService, gzippedFiles));

            int totalTasks = gzippedFiles.size();

            for (int i = 0; i < totalTasks; i++) {
                processCompletedTask(completionService, resultMap);
            }
        } catch (IOException e) {
            handleException(e);
        } finally {
            executorService.shutdown();
        }

        return resultMap;
    }

    private void processCompletedTask(CompletionService<Model> completionService, Map<String, List<CDNLogItem>> resultMap) {
        try {
            Model model = completionService.take().get();
            if (model != null) {
                resultMap.put(model.getPath().toString(), model.getItems());
            }
        } catch (InterruptedException | ExecutionException e) {
            handleException(e);
        }
    }

    private void handleException(Exception e) {
        e.printStackTrace();
    }

    private Model parseGzippedFile(Path path) {
        try (InputStream inputStream = new GZIPInputStream(Files.newInputStream(path));
             BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) {

            CDNLogItemParserCxyImpl CDNLogItemParser = new CDNLogItemParserCxyImpl();
            List<CDNLogItem> items = reader.lines()
                    .map(CDNLogItemParser::parse)
                    .filter(Optional::isPresent)
                    .map(Optional::get)
                    .collect(Collectors.toList());

            return new Model(path, items);
        } catch (IOException exception) {
            handleException(exception);
            return null;
        }
    }

    protected static class Model {
        private final Path path;
        private final List<CDNLogItem> items;

        public Model(Path path) {
            this(path, new ArrayList<>());
        }

        public Model(Path path, List<CDNLogItem> items) {
            this.path = path;
            this.items = items;
        }

        public Path getPath() {
            return path;
        }

        public List<CDNLogItem> getItems() {
            return items;
        }
    }

    private class GzFileVisitor extends SimpleFileVisitor<Path> {
        private final CompletionService<Model> completionService;
        private final List<Path> gzippedFiles;

        public GzFileVisitor(CompletionService<Model> completionService, List<Path> gzippedFiles) {
            this.completionService = completionService;
            this.gzippedFiles = gzippedFiles;
        }

        @Override
        public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
            if (file.toString().endsWith(".gz")) {
                gzippedFiles.add(file); // Store the gzipped file path
                Callable<Model> task = () -> parseGzippedFile(file);
                completionService.submit(task);
            }
            return FileVisitResult.CONTINUE;
        }
    }
}

